m<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			body {
				margin: 0;
			}

			input,
			button {
				outline: none;
				border: none;
				vertical-align: middle;
			}

			.container {
				width: 1000px;
				margin: 0 auto;
			}

			.video-wrapper {
				position: relative;
			}

			.video-wrapper video {
				width: 100%;
			}

			.video-wrapper canvas {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
			}

			.container .tool-box {
				height: 38px;
			}

			.container input,
			.container button {
				height: 100%;
				margin-right: 15px;
				border-radius: 5px;
			}

			.container .danmu-input {
				width: 300px;
				border: 1px solid #ccc;
			}

			.container .danmu-btn {
				color: #fff;
				background-color: orange;
				cursor: pointer;
				transition: 0.1s;
			}

			.danmu-btn:active {
				opacity: 0.8;
			}
		</style>
	</head>
	<body>
		<div class="container">
			<div class="video-wrapper">
				<canvas id="danmuCanvas"></canvas>
				<video 
					id="danmuVideo"
					controls
					src="https://stream7.iqilu.com/10339/article/202002/18/2fca1c77730e54c7b500573c2437003f.mp4"></video>
			</div>
			<div class="tool-box">
				<input type="text" class="danmu-input" />
				<button class="danmu-btn">发送弹幕</button>
				<input type="color" class="color-input">
			</div>
		</div>
		<script>
			function isObject(value) {
				return Object.prototype.toString.call(value) === "[object Object]";
			};
			function isArray(value) {
				return Array.isArray(value);
			}
			function getTextWidth(content, fontSize) {
				return content.length * fontSize;
			}
			function getTextPosition(canvas, fontSize, ctx) {
				let X = canvas.width,
						Y = canvas.height * Math.random();
				Y < fontSize && (Y = fontSize);
				Y > canvas.height - fontSize && (Y = canvas.height - fontSize);
				ctx.X = X;
				ctx.Y = Y;
			}
			class VideoDanmu {
				constructor(video, canvas, options) {
					if (!video || !canvas || !options || !isObject(options)) return;
					if (!options.danmuData || !isArray(options.danmuData)) return;
					this.video = video;
					this.canvas = canvas;
					this.canvasCtx = canvas.getContext('2d');
					this.canvas.width = video.offsetWidth;
					this.canvas.height = video.offsetHeight;
					this.danmuPaused = true;
					Object.assign(this, {
						speed: 2,
						runTime: 0,
						color: "#fff"
					}, options)
					this.danmuPool = this.createDanmuPool();
					this.render();
				}
				
				createDanmuPool() {
					return this.danmuData.map(danmu => new Danmu(danmu, this));
				}
				
				render() {
					this.clearRect();
					this.darwDanmu();
					!this.danmuPaused && requestAnimationFrame(this.render.bind(this));
				}
				
				darwDanmu() {
					let currentTime = this.video.currentTime;
					this.danmuPool.map((danmu) => {
						if (!danmu.stopDrawing && currentTime >= danmu.runTime) {
							if (!danmu.isInitialized) {
								danmu.initialize();
								danmu.isInitialized = true;
							}
							danmu.X -= danmu.speed;
							danmu.draw();
							if (danmu.X <= danmu.width * - 1) {
								danmu.stopDrawing = true;
							}
						}
					});
				}
				
				reset() {
					this.clearRect();
					let currentTime = this.video.currentTime;
					this.danmuPool.map((danmu) => {
						danmu.stopDrawing = false;
						
						if (currentTime <= danmu.runTime) {
							danmu.isInitialized = false;
						} else {
							danmu.stopDrawing = true;
						}
					});
				}
				
				addDanmu(data) {
					this.danmuPool.push(new Danmu(data, this));
				}
				
				clearRect() {
					this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
				}
			}
			class Danmu {
				constructor(danmu, fCtx) {
					this.content = danmu.content;
					this.runTime = danmu.runTime;
					this.danmu = danmu;
					this.ctx = fCtx;
					this.initialize();
				}
				
				initialize() {
					this.color = this.danmu.color || this.ctx.color;
					this.speed = this.danmu.speed || this.ctx.speed;
					this.fontSize = 30;
					this.width = getTextWidth(this.content, this.fontSize);
					getTextPosition(this.ctx.canvas, this.fontSize, this);
				}
				
				draw() {
					this.ctx.canvasCtx.font = this.fontSize + "px 'Microsoft Yahei'";
					this.ctx.canvasCtx.fillStyle = this.color;
					this.ctx.canvasCtx.fillText(this.content, this.X, this.Y);
				}
			}
		</script>
		<script>
			const danmuData = [{
					content: '我真的好喜欢这首钢琴曲',
					speed: 2,
					runTime: 0,
					color: 'red',
				},
				{
					content: '这首曲子是『红猪』里的"一去不复返的时光！',
					speed: 4,
					runTime: 5,
					color: 'green',
				},
				{
					content: '这首曲子是『红猪，里的"一去不复返的时光1"',
					speed: 4,
					runTime: 8,
					color: 'orange',
				},
				{
					content: '这首曲子是『红猪』里的"一去不复返的时光2',
					speed: 4,
					runTime: 8,
					color: 'blue',
				},
				{
					content: '这首曲子是『红猪』里的"一去不复返的光3"',
					speed: 4,
					runTime: 8,
					color: 'green',
				},
				{
					content: '这首曲子是『红猪』里的"一去不复返的时光4"',
					speed: 4,
					runTime: 10,
					color: 'red'
				},
				{
					content: '这首曲子是『红猪』里的"一去不复返的时光"',
					speed: 4,
					runTime: 10,
					color: 'green'
				}
			];
			
			;((doc) => {
				const oDanmuVideo = doc.getElementById("danmuVideo"),
							oDanmuCanvas = doc.getElementById("danmuCanvas"),
							oDanmuInput = doc.getElementsByClassName("danmu-input")[0],
							oDanmuBtn = doc.getElementsByClassName("danmu-btn")[0],
							oColorInput = doc.getElementsByClassName("color-input")[0];
							
				function init() {
					window.videoDanmu = new VideoDanmu(
						oDanmuVideo, 
						oDanmuCanvas,
						{
							danmuData
						}
					);
					
					bindEvent();
				};
				function bindEvent() {
					oDanmuVideo.addEventListener("play", handleVideoPlay, false);
					oDanmuVideo.addEventListener("pause", handleVideoPause, false);
					oDanmuVideo.addEventListener("seeked", handleVideoSeek, false);
					oDanmuBtn.addEventListener("click", handleDanmuBtnClick, false);
				}
				
				function handleVideoPlay() {
					videoDanmu.danmuPaused = false;
					videoDanmu.render();
				}
				
				function handleVideoPause() {
					videoDanmu.danmuPaused = true;
				}
				
				function handleVideoSeek() {
					videoDanmu.reset();
				}
				
				function handleDanmuBtnClick() {
					const inputValue = oDanmuInput.value.trim();
					if (!inputValue.length) return;
					const colorValue = oColorInput.value,
								currentTime = oDanmuVideo.currentTime;
					const _data = {
						content: inputValue,
						color: colorValue,
						runTime: currentTime
					};
					videoDanmu.addDanmu(_data);
					oDanmuInput.value = "";
				}
				
				init();
			})(document);
		</script>
	</body>
</html>